package org.deltaset.meta.internal;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.deltaset.meta.EntityResolver;
import org.deltaset.meta.MetaAction;
import org.deltaset.meta.annotation.DsOrder;
import org.deltaset.meta.annotation.DsRepository;

/**
 * @author dmakariev
 */
class ActionClassifierFactory {

    private final Map<ActionClassifierType, List<MetaAction>> actionGroupMap;
    private final MetaAction findByIdAction;
    private final MetaAction defaultFinder;
    private final List<MetaAction> allMetaActions;
    

    ActionClassifierFactory(final Class entityClass, final Class defaultRepositoryClass, final EntityResolver entityResolver) {
        final List<ActionClassifier> metaActions = extractMetaActions(entityClass, defaultRepositoryClass, entityResolver);
        this.allMetaActions = allMetaActions(metaActions);
        this.findByIdAction = initFindByIdAction(metaActions, entityClass);
        this.defaultFinder = initDefaultFinder(metaActions, entityClass);
        this.actionGroupMap = groupActions(metaActions);

    }

    public Map<ActionClassifierType, List<MetaAction>> getActionGroupMap() {
        return actionGroupMap;
    }

    public MetaAction getDefaultFinder() {
        return defaultFinder;
    }

    public MetaAction getFindByIdAction() {
        return findByIdAction;
    }
    
    public List<MetaAction> getAllMetaActions(){
        return allMetaActions;
    }

////////////
    private static List<ActionClassifier> extractMetaActions(final Class entityClass, final Class defaultRepositoryClass, final EntityResolver entityResolver) {
        final List<ActionClassifier> metaActions = new ArrayList<ActionClassifier>();
        final List<Method> methods = new ArrayList<Method>();
        methods.addAll(ReflectionUtils.findActions(entityClass));
        for (final Method method : methods) {
            final ActionClassifier metaAction = newMetaAction(method, entityClass, entityClass, entityResolver);
            metaActions.add(metaAction);
        }
        metaActions.addAll(extractRepositoryActions(entityClass, defaultRepositoryClass, entityResolver));
        return metaActions;
    }
    
    private static List<MetaAction> allMetaActions(final List<ActionClassifier> groups){
        final List<MetaAction> allMetaActions = new ArrayList<MetaAction>();
        if(null!=groups&&!groups.isEmpty()){
            for (ActionClassifier group : groups) {
                allMetaActions.add(group.getMetaAction());
            }
        }
        return allMetaActions;
    }

    private static ActionClassifier newMetaAction(final Method method, final Class entityClass, final Class executeClass, final EntityResolver entityResolver) {
        final Set<MetaActionType> typeSet = MetaActionTypeSetFactory.createSet(method, entityClass, executeClass);
        final String order = extractOrder(method);
        final ActionClassifier group = new ActionClassifier(typeSet, order, entityClass, executeClass, method, entityResolver);
        return group;
        // return new MetaAction(entityClass, executeClass, method);
    }

    private static List<ActionClassifier> extractRepositoryActions(final Class entityClass, final Class defaultRepositoryClass, final EntityResolver entityResolver) {
        final Class[] repositoryClasses = extractRepositoryClasses(entityClass, defaultRepositoryClass);
        final List<ActionClassifier> metaActions = new ArrayList<ActionClassifier>();
        for (Class repositoryClass : repositoryClasses) {
            final List<Method> methods = ReflectionUtils.findActions(repositoryClass);
            for (Method method : methods) {
                metaActions.add(newMetaAction(method, entityClass, repositoryClass, entityResolver));
            }
        }
        return metaActions;
    }

////////////
    private static Map<ActionClassifierType, List<MetaAction>> groupActions(final List<ActionClassifier> metaActions) {
        final List<MetaAction> otherActions = initOtherActions(metaActions);
        final List<MetaAction> otherContextActions = initOtherContextActions(metaActions);
        final List<MetaAction> factoryActions = initFactoryActions(metaActions);
        final List<MetaAction> contextActions = initContextActions(metaActions);
        final List<MetaAction> finderActions = initFinderActions(metaActions);

        final Map<ActionClassifierType, List<MetaAction>> actionMap = new EnumMap<ActionClassifierType, List<MetaAction>>(ActionClassifierType.class);
        actionMap.put(ActionClassifierType.OTHER, otherActions);
        actionMap.put(ActionClassifierType.OTHER_CONTEXT, otherContextActions);
        actionMap.put(ActionClassifierType.FACTORY, factoryActions);
        actionMap.put(ActionClassifierType.CONTEXT, contextActions);
        actionMap.put(ActionClassifierType.FINDER, finderActions);
        return actionMap;
    }

    private static String extractOrder(final Method method) {
        final DsOrder dsOrder = method.getAnnotation(DsOrder.class);
        if (null != dsOrder) {
            return dsOrder.value();
        }
        return method.getName();
    }

    /**
     * defines 'strategy' for 'finding' the repository classes. 
     * if there is no DsRepository annotation 
     * checks if exists type(class/interface) [Entity]Repository,
     * if there is [Entity]RepositoryAddOn this is additional to either [Entity]Repository or to the defaultRepository
     */
    private static Class[] extractRepositoryClasses(final Class entityClass, final Class defaultRepositoryClass) {
        final DsRepository dRepository = (DsRepository) entityClass.getAnnotation(DsRepository.class);
        if (null == dRepository) {
            //final Class repositoryClass = RepositoryRegistry.defaultRepository();//ModelRepository.class;
            final String repositoryClassByConventionName = entityClass.getName() + "Repository";
            final String addOnClassByConventionName = entityClass.getName() + "RepositoryAddon";

            final Class repositoryClassByConvention = forNameOrNull(repositoryClassByConventionName);
            final Class useRepositoryClass = null != repositoryClassByConvention ? repositoryClassByConvention : defaultRepositoryClass;

            final Class addOnClassByConvention = forNameOrNull(addOnClassByConventionName);
            final boolean hasAddOnClass = null != addOnClassByConvention;
            if (hasAddOnClass) {
                return new Class[]{useRepositoryClass, addOnClassByConvention};
            } else {
                return new Class[]{useRepositoryClass};
            }


        }

        final Class[] repositoryClasses = dRepository.value();
        return repositoryClasses;
    }

    private static Class forNameOrNull(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException ex) {
            //class not found - do nothing.
        }
        return null;
    }

    private static List<MetaAction> sortedUnmodifiableList(final List<ActionClassifier> src) {
        final List<ActionClassifier> sortedList = new ArrayList<ActionClassifier>(src);
        Collections.sort(sortedList, new MetaActionComparator());
        final List<MetaAction> result = new ArrayList<MetaAction>();
        for (ActionClassifier group : sortedList) {
            result.add(group.getMetaAction());
        }
        return Collections.unmodifiableList(result);
    }

    private static class MetaActionComparator implements Comparator<ActionClassifier> {

        @Override
        public int compare(ActionClassifier o1, ActionClassifier o2) {
            final String order1 = o1.getOrder();
            final String order2 = o2.getOrder();
            return order1.compareToIgnoreCase(order2);
        }
    }

    private static MetaAction initFindByIdAction(List<ActionClassifier> metaActions, Class entityClass) {
        for (ActionClassifier metaAction : metaActions) {
            if (metaAction.isFindById()) {
                return metaAction.getMetaAction();
            }
        }
        throw new RuntimeException("ERROR cannot find 'findById' for class=" + entityClass.getName());
    }

    private static MetaAction initDefaultFinder(List<ActionClassifier> metaActions, Class entityClass) {
        for (ActionClassifier metaAction : metaActions) {
            if (metaAction.isDefaultFinder()) {
                return metaAction.getMetaAction();
            }
        }
        throw new RuntimeException("ERROR cannot find 'DEFAULT_FINDER' for class=" + entityClass.getName());
    }

    private static List<MetaAction> initOtherActions(List<ActionClassifier> metaActions) {
        final List<ActionClassifier> selectedActions = new ArrayList<ActionClassifier>();
        for (ActionClassifier metaAction : metaActions) {
            if (metaAction.isOther()) {
                selectedActions.add(metaAction);
            }
        }
        return sortedUnmodifiableList(selectedActions);
    }

    private static List<MetaAction> initOtherContextActions(List<ActionClassifier> metaActions) {
        final List<ActionClassifier> selectedActions = new ArrayList<ActionClassifier>();
        for (ActionClassifier metaAction : metaActions) {
            if (metaAction.isOtherContext()) {
                selectedActions.add(metaAction);
            }
        }
        return sortedUnmodifiableList(selectedActions);
    }

    private static List<MetaAction> initFactoryActions(List<ActionClassifier> metaActions) {
        final List<ActionClassifier> selectedActions = new ArrayList<ActionClassifier>();
        for (ActionClassifier metaAction : metaActions) {
            if (metaAction.isFactory()) {
                selectedActions.add(metaAction);
            }
        }
        return sortedUnmodifiableList(selectedActions);
    }

    private static List<MetaAction> initContextActions(List<ActionClassifier> metaActions) {
        final List<ActionClassifier> selectedActions = new ArrayList<ActionClassifier>();
        for (ActionClassifier metaAction : metaActions) {
            if (metaAction.isContext() && (metaAction.isReturningVoid() || metaAction.isReturningEntity())) {
                selectedActions.add(metaAction);
            }
        }
        return sortedUnmodifiableList(selectedActions);
    }

    private static List<MetaAction> initFinderActions(List<ActionClassifier> metaActions) {
        final List<ActionClassifier> selectedActions = new ArrayList<ActionClassifier>();
        for (ActionClassifier metaAction : metaActions) {
            if (metaAction.isFinder()) {
                selectedActions.add(metaAction);
            }
        }
        return sortedUnmodifiableList(selectedActions);
    }
}
